#include "Middlewares/FATFS/ff_exfuns.h"
#include "Middlewares/FATFS/ffconf.h"

#include "stdio.h"
#include "string.h"

#define FILE_MAX_TYPE_NUM 7 /* 最多FILE_MAX_TYPE_NUM个大类 */
#define FILE_MAX_SUBT_NUM 4 /* 最多FILE_MAX_SUBT_NUM个小类 */

/* 文件类型列表 */
static char *const FILE_TYPE_TBL[FILE_MAX_TYPE_NUM][FILE_MAX_SUBT_NUM] =
    {
        {"BIN"},                       /* BIN文件 */
        {"LRC"},                       /* LRC文件 */
        {"NES", "SMS"},                /* NES/SMS文件 */
        {"TXT", "C", "H"},             /* 文本文件 */
        {"WAV", "MP3", "FLAC", "APE"}, /* 支持的音乐文件 */
        {"BMP", "JPG", "JPEG", "GIF"}, /* 图片文件 */
        {"AVI"},                       /* 视频文件 */
};

FATFS *fs[FF_VOLUMES]; /* 使用动态分配的文件系统对象指针,使用前需要初始化申请内存 */

/**
 * @brief    FATFS文件系统初始化
 * @param    无
 * @retval   0, 成功; 其他, 失败代码;
 */
uint8_t fs_init(void)
{
    uint8_t res;

    for (uint8_t i = 0; i < FF_VOLUMES; i++)
    {
        fs[i] = (FATFS *)FS_MALLOC(sizeof(FATFS)); /* 申请文件系统对象内存 */
        if (fs[i] == NULL)
        {
            FS_LOG("ff_exfuns: No memory for FATFS[%d]\r\n", i);
            fs_deinit(); /* 释放已申请的内存 */
            res = 1;
            return res;
        }

        memset(fs[i], 0, sizeof(FATFS));

        res = f_mount(fs[i], (const TCHAR *)(i + '0'), 0); /* 挂载文件系统 */

        if (res != FR_OK)
        {
            FS_LOG("ff_exfuns: f_mount(FATFS[%d]) fail, res = %d\r\n", i, res);
            fs_deinit(); /* 释放已申请的内存 */
            res = 2;
            return res;
        }
    }
    return 0;
}

/**
 * @brief    FATFS文件系统反初始化
 * @param    无
 * @retval   无
 */
void fs_deinit(void)
{
    for (uint8_t i = 0; i < FF_VOLUMES; i++)
    {
        if (fs[i])
        {
            f_mount(NULL, (const TCHAR *)(i + '0'), 0); /* 卸载文件系统 */
            FS_FREE(fs[i]);                             /* 释放文件系统对象内存 */
            fs[i] = NULL;
        }
    }
}

/**
 * @brief    获取指定驱动器的总容量和剩余容量
 * @param    pdrv  : 物理驱动器号
 * @param    total : 总容量指针,单位KB
 * @param    free  : 剩余容量指针,单位KB
 * @retval   0, 成功; 其他, 失败代码;
 */
uint8_t fs_get_free(uint8_t pdrv, uint32_t *total, uint32_t *free)
{
    FATFS *_fs = fs[pdrv];
    if (_fs)
    {
        DWORD fre_clust;
        if (f_getfree((const TCHAR *)(pdrv + '0'), &fre_clust, &_fs) == FR_OK)
        {
            if (total)
            {

                *total = (_fs->n_fatent - 2) * _fs->csize / 2; /* 总容量,单位KB */
            }
            if (free)
            {
                *free = (fre_clust - 2) * _fs->csize / 2; /* 剩余容量,单位KB */
            }
            return 0;
        }
        else
        {
            FS_LOG("ff_exfuns: f_getfree fail\r\n");
            return 2;
        }
    }
    else
    {
        FS_LOG("ff_exfuns: FATFS[%d] is NULL\r\n", pdrv);
        return 1;
    }
}

/**
 * @brief    获取指定目录下的文件数量
 * @param    path  : 目录路径
 * @retval   文件数量
 */
uint8_t fs_get_file_num(uint8_t *path)
{
    DIR dir;
    FILINFO fno = {0};
    FRESULT res;
    uint8_t num = 0;
    res = f_opendir(&dir, (const TCHAR *)path); /* 打开目录 */
    if (res == FR_OK)
    {
        while (1)
        {
            fno.fname[0] = 0;
            res = f_readdir(&dir, &fno); /* 读取目录项 */
            if (res != FR_OK || fno.fname[0] == 0)
                break; /* 出错或者目录结束 */
            else
                num++;
        }
        f_closedir(&dir); /* 关闭目录 */
        return num;
    }
    else
    {
        FS_LOG("ff_exfuns: f_opendir fail, res = %d\r\n", res);
        return 0;
    }
}

/**
 * @brief    获取指定目录下的文件名
 * @param    path  : 目录路径
 * @param    index : 文件索引
 * @param    name  : 文件名指针
 * @retval   0, 成功; 其他, 失败代码;
 * @note     index从1开始
 */
uint8_t fs_get_file_name(uint8_t *path, uint8_t index, char *name)
{
    DIR dir;
    FILINFO fno = {0};
    FRESULT res;
    uint8_t num = 0;

    res = f_opendir(&dir, (const TCHAR *)path); /* 打开目录 */
    if (res == FR_OK)
    {
        while (1)
        {
            fno.fname[0] = 0;
            res = f_readdir(&dir, &fno); /* 读取目录项 */
            if (res != FR_OK || fno.fname[0] == 0)
                break; /* 出错或者目录结束 */
            else
                num++;

            if (num == index)
            {
                strcpy(name, fno.fname);
                break;
            }
        }
        f_closedir(&dir); /* 关闭目录 */
        return 0;
    }
    else
    {
        FS_LOG("ff_exfuns: f_opendir fail, res = %d\r\n", res);
        return 1;
    }
}

/**
 * @brief    判断指定路径是否为目录
 * @param    path  : 路径
 * @retval   0, 不是目录; 1, 是目录; 其他, 失败代码;
 */
uint8_t fs_is_folder(uint8_t *path)
{
    FILINFO fno = {0};
    FRESULT res;

    res = f_stat((const TCHAR *)path, &fno); /* 获取文件信息 */
    if (res == FR_OK)
    {
        return fno.fattrib & AM_DIR; /* 判断是否为目录 */
    }
    // else if(res == FR_NO_FILE) // 这里做出仅适合本MP3的适配 
    // {
    //     return 0;
    // }
	else 
	{
        FS_LOG("ff_exfuns: f_stat fail, res = %d\r\n", res);
        return 1;
	}
	
}

/**
 * @brief    进入指定目录
 * @param    path  : 路径
 * @param    index : 目录索引
 * @retval   0, 成功; 其他, 失败代码;
 * @note     index从1开始
 */
uint8_t fs_enter_folder(uint8_t *path, uint8_t index)
{
    DIR dir;
    FILINFO fno = {0};
    FRESULT res;
    uint16_t num = 0;
    res = f_opendir(&dir, (const TCHAR *)path); /* 打开目录 */
    if (res == FR_OK)
    {
        while (1)
        {
            fno.fname[0] = 0;
            res = f_readdir(&dir, &fno); /* 读取目录项 */
            if (res != FR_OK || fno.fname[0] == 0)
                break; /* 出错或者目录结束 */
            else
                num++;

            if (num == index)
                sprintf((char *)path, "%s/%s", path, fno.fname);
        }
        f_closedir(&dir); /* 关闭目录 */
        return 0;
    }
    else
    {
        FS_LOG("ff_exfuns: f_opendir fail, res = %d\r\n", res);
        return 1;
    }
}

/**
 * @brief    返回上一级目录
 * @param    path  : 路径
 * @retval   0, 成功; 其他, 失败代码;
 */
uint8_t fs_back_folder(uint8_t *path)
{
    char *p = strrchr((const char *)path, '/');
    if (p)
    {
        *p = 0;
        return 0;
    }
    else
    {
        FS_LOG("ff_exfuns: strrchr fail\r\n");
        return 1;
    }
}

/**
 * @brief    获取指定文件在目录中的索引
 * @param    path  : 目录路径
 * @param    name  : 文件名
 * @retval   文件索引
 */
uint8_t fs_get_file_index(uint8_t *path, const uint8_t *name)
{
    DIR dir;
    FILINFO fno = {0};
    FRESULT res;
    uint8_t idx = 0;
    /*---- 在目录里找同名文件 ----*/
    res = f_opendir(&dir, (const char *)path);
    if (res != FR_OK)
        return 0;
    while (1)
    {
        fno.fname[0] = 0;
        res = f_readdir(&dir, &fno); // 读取下一个文件/文件夹
        if (res != FR_OK || fno.fname[0] == 0)
            break; // 读取结束
        else
            idx++;

        if (strcmp(fno.fname, (const char *)name) == 0)
        {
            f_closedir(&dir); // 关闭目录
            return idx;
        }
    }
    f_closedir(&dir); // 关闭目录
    return 0;
}

/**
 * @brief    从路径中获取文件名
 * @param    path  : 路径
 * @retval   文件名指针
 */
uint8_t *fs_get_src_name(uint8_t *path)
{
    uint16_t temp = 0;

    while (*path != 0)
    {
        path++;
        temp++;
    }

    if (temp < 4)
        return 0;

    while ((*path != 0x5c) && (*path != 0x2f))
        path--; /* 追述到倒数第一个"\"或者"/"处 */

    return ++path;
}

/**
 * @brief       报告文件的类型
 * @param       fname : 文件名
 * @retval      文件类型
 *   @arg       0XFF , 表示无法识别的文件类型编号.
 *   @arg       其他 , 高四位表示所属大类, 低四位表示所属小类.
 */
uint8_t fs_get_file_type(uint8_t *fname)
{
    uint8_t tbuf[5];
    uint8_t *attr = 0; /* 后缀名 */
    uint8_t i = 0, j;

    while (i < 250)
    {
        i++;

        if (*fname == '\0')
            break; /* 偏移到了最后了. */

        fname++;
    }

    if (i == 250)
        return 0XFF; /* 错误的字符串. */

    for (i = 0; i < 5; i++) /* 得到后缀名 */
    {
        fname--;

        if (*fname == '.')
        {
            fname++;
            attr = fname;
            break;
        }
    }

    if (attr == 0)
        return 0XFF;

    strcpy((char *)tbuf, (const char *)attr); /* copy */

    for (i = 0; i < 4; i++)
    {
        if (tbuf[i] >= 'a')
        {
            tbuf[i] -= 0x20; /* 变为大写. */
        }
    }

    for (i = 0; i < FILE_MAX_TYPE_NUM; i++) /* 大类对比 */
    {
        for (j = 0; j < FILE_MAX_SUBT_NUM; j++) /* 子类对比 */
        {
            if (*FILE_TYPE_TBL[i][j] == 0)
                break; /* 此组已经没有可对比的成员了. */

            if (strcmp((const char *)FILE_TYPE_TBL[i][j], (const char *)tbuf) == 0) /* 找到了 */
            {
                return (i << 4) | j;
            }
        }
    }

    return 0XFF; /* 没找到 */
}
